﻿using System;
using System.Drawing;
using GeoSketch2.Core;
using System.Xml;
using System.IO;
using System.Collections.Generic;

namespace GeoSketch2.Archaeo
{
    /// <summary>
    /// An image for the Archaeological module, contains its transformation beetween image coordinates and object coordinates
    /// </summary>
    public class GeoTaggedImage: BaseArchaeoDrawableObject
    {
        #region XMLElementsName
#pragma warning disable 1591
        public const string XML_FileName = "FileName";
        public const string XML_Extents = "Extents";
#pragma warning restore 1591
        #endregion

        #region Fields
        /// <summary>
        /// The image
        /// </summary>
        public Bitmap Image;

        /// <summary>
        /// The transformation beetween object coordinates and image coodinates
        /// </summary>
        public Transformations.AffineTransformation2D Object2ImageTransformation;

        private RectangleD _ImageExtents;

        /// <summary>
        /// The full filename of the picture
        /// </summary>
        public string FullFilename;
        #endregion

        #region Constructor
        /// <summary>
        /// An image for the Archaeological module, contains its transformation beetween image coordinates and object coodinates
        /// </summary>
        /// <param name="ImageFullfileName">he full filename of the picture</param>
        /// <param name="ImageExtents">The extents of the image in object coordinates, y axis upside</param>
        /// <param name="Layer">The layer of the object</param>
        /// <param name="ExcavationUnit">The excavation unit of the object</param>
        public GeoTaggedImage(string ImageFullfileName, RectangleD ImageExtents, Layer Layer, ExcavationArea ExcavationArea)
            : this(ImageFullfileName, ImageExtents, Layer, ExcavationArea, "")
        {
        }

        /// <summary>
        /// An image for the Archaeological module, contains its transformation beetween image coordinates and object coodinates
        /// </summary>
        /// <param name="ImageFullfileName">he full filename of the picture</param>
        /// <param name="ImageExtents">The extents of the image in object coordinates, y axis upside</param>
        /// <param name="Layer">The layer of the object</param>
        /// <param name="ExcavationUnit">The excavation unit of the object</param>
        /// <param name="Comment">The comment about of the point</param>
        public GeoTaggedImage(string ImageFullfileName, RectangleD ImageExtents, Layer Layer, ExcavationArea ExcavationArea, string Comment)
            : this(ImageFullfileName, ImageExtents, Layer, ExcavationArea, Comment, DateTime.Now)
        {
        }

        /// <summary>
        /// An image for the Archaeological module, contains its transformation beetween image coordinates and object coodinates
        /// </summary>
        /// <param name="ImageFullfileName">he full filename of the picture</param>
        /// <param name="ImageExtents">The extents of the image in object coordinates, y axis upside</param>
        /// <param name="Layer">The layer of the object</param>
        /// <param name="ExcavationUnit">The excavation unit of the object</param>
        /// <param name="Comment">The comment about of the point</param>
        /// <param name="CreationTime">The date of creation of the object</param>
        public GeoTaggedImage(string ImageFullfileName, RectangleD ImageExtents, Layer Layer, ExcavationArea ExcavationArea, string Comment, DateTime CreationTime)
            : base(Layer, ColorByLayer(), LineTypeByLayer(), ExcavationArea, Comment, CreationTime)
        {
            this.Image = new Bitmap(ImageFullfileName);
            this.FullFilename = ImageFullfileName;
            Object2ImageTransformation = new Transformations.AffineTransformation2D();
            this.ImageExtents = ImageExtents;
        }

        /// <summary>
        /// An image for the Archaeological module, contains its transformation beetween image coordinates and object coodinates
        /// </summary>
        /// <param name="Xr">The XmlReader to read from</param>
        /// <param name="Site">The site of the image</param>
        public GeoTaggedImage(XmlReader Xr, ArchaeoProject Project)
        :base(Xr,Project)
        {
            Object2ImageTransformation = new Transformations.AffineTransformation2D();

            Xr.ReadToFollowing(XML_SpecificInfos);

            #region FileName
            Xr.ReadToFollowing(XML_FileName);
            this.FullFilename = Project.ProjectDirectory + "\\Images\\" + Xr.ReadString();
            this.Image = new Bitmap(this.FullFilename); 
            #endregion

            #region Extents
            Xr.ReadToFollowing(XML_Extents);
            Xr.ReadToFollowing(typeof(RectangleD).FullName);
            this.ImageExtents = new RectangleD(Xr); 
            #endregion

            Xr.ReadEndElement();
            Xr.ReadEndElement();
            Xr.ReadEndElement();
        }
        #endregion

        #region Methods
        /// <summary>
        /// The draw method
        /// </summary>
        /// <param name="Viewport">The viewport to draw to</param>
        protected override void DrawUnselected(BaseViewport Viewport)
        {
            DrawCustomPen(Viewport, new Pen(Color.Transparent, 0f));
        }

        /// <summary>
        /// The draw method
        /// </summary>
        /// <param name="Viewport">The viewport to draw to</param>
        protected override void DrawSelected(BaseViewport Viewport)
        {
            DrawCustomPen(Viewport, Core.Vars.Setup.Tools.TSelection.SelectionPen);
        }

        /// <summary>
        /// The draw method for each object with a custom pen
        /// </summary>
        /// <param name="Viewport">The viewport to draw to</param>
        /// <param name="CustomPen">The custom pen object to draw the object with</param>
        public override void DrawCustomPen(BaseViewport Viewport, Pen CustomPen)
        {
            if (Viewport.CurrentObjectView.Width > _ImageExtents.Width || Viewport.CurrentObjectView.Height > _ImageExtents.Height)
            {
                RectangleD ImageExtentsOnClientRectangle = Viewport.Object2ImageTransformation.Transform(_ImageExtents, YAxisDirection.DownSide);
                Viewport.Graphic.DrawImage(Image, (RectangleF)ImageExtentsOnClientRectangle, Image.GetImageBounds(), GraphicsUnit.Pixel);
                Viewport.Graphic.DrawRectangle(CustomPen, (Rectangle)Viewport.Object2ImageTransformation.Transform(_ImageExtents, YAxisDirection.DownSide));
            }
            else
            {
                RectangleD ClientRectangle = Viewport.DisplayControl.ClientRectangle;
                RectangleD ClientRectangleOnImage = Viewport.Object2ImageTransformation.InverseTransform(ClientRectangle, YAxisDirection.UpSide);
                ClientRectangleOnImage = Object2ImageTransformation.Transform(ClientRectangleOnImage, YAxisDirection.DownSide);
                Viewport.Graphic.DrawImage(Image, (RectangleF)ClientRectangle, (RectangleF)ClientRectangleOnImage, GraphicsUnit.Pixel);
                Viewport.Graphic.DrawRectangle(CustomPen, (Rectangle)Viewport.Object2ImageTransformation.Transform(_ImageExtents, YAxisDirection.DownSide));
            }
        }

        /// <summary>
        /// The method to test if an object is near to the point clicked, for an object selection purpose
        /// </summary>
        /// <param name="Clic">The point clicked in object coordinates</param>
        /// <param name="IsNearDistance">The distance under which the clic is near to the object</param>
        public override bool IsNear(Point2D64D Clic, double IsNearDistance)
        {
            Point2D64D center;

            if (Clic.DistancePointLine(_ImageExtents.UpperLeftPoint, _ImageExtents.UpperRightPoint) < IsNearDistance)
            {
                center = (_ImageExtents.UpperLeftPoint + _ImageExtents.UpperRightPoint) * 0.5;

                if (2 * center.Distance2DSqr(Clic) < _ImageExtents.UpperLeftPoint.Distance2DSqr(_ImageExtents.UpperRightPoint) + Core.Vars.Setup.Tools.TSelection.IsNearDistance * Core.Vars.Setup.Tools.TSelection.IsNearDistance)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else if (Clic.DistancePointLine(_ImageExtents.UpperRightPoint, _ImageExtents.LowerRightPoint) < IsNearDistance)
            {
                center = (_ImageExtents.UpperRightPoint + _ImageExtents.LowerRightPoint) * 0.5;

                if (2 * center.Distance2DSqr(Clic) < _ImageExtents.UpperRightPoint.Distance2DSqr(_ImageExtents.LowerRightPoint) + Core.Vars.Setup.Tools.TSelection.IsNearDistance * Core.Vars.Setup.Tools.TSelection.IsNearDistance)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else if (Clic.DistancePointLine(_ImageExtents.LowerRightPoint, _ImageExtents.LowerLeftPoint) < IsNearDistance)
            {
                center = (_ImageExtents.LowerRightPoint + _ImageExtents.LowerLeftPoint) * 0.5;

                if (2 * center.Distance2DSqr(Clic) < _ImageExtents.LowerRightPoint.Distance2DSqr(_ImageExtents.LowerLeftPoint) + Core.Vars.Setup.Tools.TSelection.IsNearDistance * Core.Vars.Setup.Tools.TSelection.IsNearDistance)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else if (Clic.DistancePointLine(_ImageExtents.LowerLeftPoint, _ImageExtents.UpperLeftPoint) < IsNearDistance)
            {
                center = (_ImageExtents.LowerLeftPoint + _ImageExtents.UpperLeftPoint) * 0.5;

                if (2 * center.Distance2DSqr(Clic) < _ImageExtents.LowerLeftPoint.Distance2DSqr(_ImageExtents.UpperLeftPoint) + Core.Vars.Setup.Tools.TSelection.IsNearDistance * Core.Vars.Setup.Tools.TSelection.IsNearDistance)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Returns the nearest line to the click, null if none
        /// </summary>
        /// <param name="Clic">The point clicked in object coordinates</param>
        /// <param name="IsNearDistance">The distance under which the clic is near to the object</param>
        public override Line2D64D NearestLine(Point2D64D Clic, double IsNearDistance)
        {
            Point2D64D center;

            if (Clic.DistancePointLine(_ImageExtents.UpperLeftPoint, _ImageExtents.UpperRightPoint) < IsNearDistance)
            {
                center = (_ImageExtents.UpperLeftPoint + _ImageExtents.UpperRightPoint) * 0.5;

                if (2 * center.Distance2DSqr(Clic) < _ImageExtents.UpperLeftPoint.Distance2DSqr(_ImageExtents.UpperRightPoint) + Core.Vars.Setup.Tools.TSelection.IsNearDistance * Core.Vars.Setup.Tools.TSelection.IsNearDistance)
                {
                    return new Line2D64D(_ImageExtents.UpperLeftPoint, _ImageExtents.UpperRightPoint);
                }
                else
                {
                    return null;
                }
            }
            else if (Clic.DistancePointLine(_ImageExtents.UpperRightPoint, _ImageExtents.LowerRightPoint) < IsNearDistance)
            {
                center = (_ImageExtents.UpperRightPoint + _ImageExtents.LowerRightPoint) * 0.5;

                if (2 * center.Distance2DSqr(Clic) < _ImageExtents.UpperRightPoint.Distance2DSqr(_ImageExtents.LowerRightPoint) + Core.Vars.Setup.Tools.TSelection.IsNearDistance * Core.Vars.Setup.Tools.TSelection.IsNearDistance)
                {
                    return new Line2D64D(_ImageExtents.UpperRightPoint, _ImageExtents.LowerRightPoint);
                }
                else
                {
                    return null;
                }
            }
            else if (Clic.DistancePointLine(_ImageExtents.LowerRightPoint, _ImageExtents.LowerLeftPoint) < IsNearDistance)
            {
                center = (_ImageExtents.LowerRightPoint + _ImageExtents.LowerLeftPoint) * 0.5;

                if (2 * center.Distance2DSqr(Clic) < _ImageExtents.LowerRightPoint.Distance2DSqr(_ImageExtents.LowerLeftPoint) + Core.Vars.Setup.Tools.TSelection.IsNearDistance * Core.Vars.Setup.Tools.TSelection.IsNearDistance)
                {
                    return new Line2D64D(_ImageExtents.LowerRightPoint, _ImageExtents.LowerLeftPoint);
                }
                else
                {
                    return null;
                }
            }
            else if (Clic.DistancePointLine(_ImageExtents.LowerLeftPoint, _ImageExtents.UpperLeftPoint) < IsNearDistance)
            {
                center = (_ImageExtents.LowerLeftPoint + _ImageExtents.UpperLeftPoint) * 0.5;

                if (2 * center.Distance2DSqr(Clic) < _ImageExtents.LowerLeftPoint.Distance2DSqr(_ImageExtents.UpperLeftPoint) + Core.Vars.Setup.Tools.TSelection.IsNearDistance * Core.Vars.Setup.Tools.TSelection.IsNearDistance)
                {
                    return new Line2D64D(_ImageExtents.LowerLeftPoint, _ImageExtents.UpperLeftPoint);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Returns the nearest point to the click, null if none
        /// </summary>
        /// <param name="Clic">The point clicked in object coordinates</param>
        /// <param name="IsNearDistance">The distance under which the clic is near to the object</param>
        public override Point2D64D NearestPoint(Point2D64D Clic, double IsNearDistance)
        {
            if (Clic.Distance2D(_ImageExtents.LowerLeftPoint) < IsNearDistance)
            {
                return _ImageExtents.LowerLeftPoint;
            }
            else if (Clic.Distance2D(_ImageExtents.LowerRightPoint) < IsNearDistance)
            {
                return _ImageExtents.LowerRightPoint;
            }
            else if (Clic.Distance2D(_ImageExtents.UpperLeftPoint) < IsNearDistance)
            {
                return _ImageExtents.UpperLeftPoint;
            }
            else if (Clic.Distance2D(_ImageExtents.UpperRightPoint) < IsNearDistance)
            {
                return _ImageExtents.UpperRightPoint;
            }
            else
            {
                return null;
            }
        }
        
        //Computes the transformation between the object coordinates of the image and the images coordinates
        private void ComputeTransformation(Bitmap img)
        {
            if (ImageExtents.Width > 0 && ImageExtents.Height > 0)
            {
                Object2ImageTransformation.ComputeTranformationFromTargetArea(_ImageExtents, new Rectangle(new Point(), img.Size));
            }
        }

        /// <summary>
        /// Clones the object
        /// </summary>
        public override T Clone<T>()
        {
            return (T)(object)new GeoTaggedImage(FullFilename, ImageExtents.DeepClone(), Layer, ExcavationArea, Comment);
        }

        /// <summary>
        /// Translates the object
        /// </summary>
        /// <param name="Vector">The translation vector</param>
        public override void Translate(Point2D64D Vector)
        {
            ImageExtents.X += Vector.X;
            ImageExtents.Y += Vector.Y;
        }

        /// <summary>
        /// Writes the object to a file
        /// </summary>
        /// <param name="Xw">The XmlWriter to write to</param>
        public override void ToFile(XmlWriter Xw)
        {
            Xw.WriteStartElement(this.GetType().FullName);

            #region Object Base Information
            this.BaseInfosToFile(Xw);
            #endregion

            #region Object Specific Information
            Xw.WriteStartElement(XML_SpecificInfos);

            #region Filename
            Xw.WriteStartElement(XML_FileName);
            Xw.WriteString(this.Filename);
            Xw.WriteEndElement();
            #endregion

            #region Extents
            Xw.WriteStartElement(XML_Extents);
            this.ImageExtents.ToFile(Xw);
            Xw.WriteEndElement();
            #endregion

            Xw.WriteEndElement();
            #endregion

            Xw.WriteEndElement();
        }

        public override void ToDXFr13(TextWriter Tw)
        {
            new Polyline(new List<Vertex>(new Vertex[]{
                new Vertex(ImageExtents.LowerLeftPoint,VertexType.Line),
                new Vertex(ImageExtents.LowerRightPoint,VertexType.Line),
                new Vertex(ImageExtents.UpperRightPoint,VertexType.Line),
                new Vertex(ImageExtents.UpperLeftPoint,VertexType.Line),
                new Vertex(ImageExtents.LowerLeftPoint,VertexType.Line)}),
                this.Layer, this.ExcavationArea, this._LineType, this._ObjectColor).ToDXFr13(Tw);

            new InputText(ImageExtents.Center, "Image : " + this.Filename, this.Layer, this.ExcavationArea, this._ObjectColor).ToDXFr13(Tw);
        }
        #endregion

        #region Properties
        /// <summary>
        /// The extents of the image in object coordinates, the rectangle must be upside oriented
        /// </summary>
        public RectangleD ImageExtents
        {
            get
            {
                return _ImageExtents;
            }
            set
            {
                _ImageExtents = value;
                ComputeTransformation(Image);
            }
        }

        /// <summary>
        /// Gets the bounding box of the object, the Y-axis direction of the REctangleD must be upside
        /// </summary>
        public override RectangleD Bounds
        {
            get
            {
                return ImageExtents;
            }
        }

        /// <summary>
        /// Gets the filename of the picture, without the path
        /// </summary>
        public string Filename
        {
            get
            {
                return Path.GetFileName(FullFilename);
            }
        }
        #endregion
    }
}
